# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: xguo

from coin.exchange.gdax.kr_rest.native_private_client import (
    GdaxNativePrivateClient,
    GdaxOrderStatus,
)
from coin.exchange.gdax.kr_rest.product import GdaxProduct, GdaxCurrency
import coin.exchange.base.kr_rest.private_client_base as privcb
import dateutil.parser
from coin.proto.coin_query_pb2 import (
    AccountBalance,
    CurrencyBalance,
    ProductOrderBundle,
    ProductOrderElement,
    CurrencyTransferBundle,
    CurrencyTransferElement,
)


class GdaxPrivateParser(object):
  @staticmethod
  def parse_balance(balance_msg):
    currency = GdaxCurrency.FromStrNativeCurrencyNothrow(balance_msg['currency'])
    if currency is None:
      return

    total = float(balance_msg['balance'])
    available = float(balance_msg['available'])
    hold = float(balance_msg['hold'])
    return CurrencyBalance(currency=currency.currency,
                           currency_native=currency.native_currency,
                           total=total,
                           available=available,
                           hold=hold)

  @staticmethod
  def parse_account_balance(account_balance_msg):
    balance_list = []
    for balance_msg in account_balance_msg:
      balance = GdaxPrivateParser.parse_balance(balance_msg)
      if balance is not None:
        balance_list.append(balance)
    return AccountBalance(exchange='Gdax', each_balance=balance_list)

  @staticmethod
  def parse_order(order_msg):
    """
    {
    "id": "a99ead18-dcef-4f79-9974-a5934aefa43d",
    "product_id": "BTC-USD",
    "side": "buy",
    "funds": "0.9975062300000000",
    "specified_funds": "1.0000000000000000",
    "type": "market",
    "post_only": false,
    "created_at": "2018-03-22T08:12:18.244557Z",
    "done_at": "2018-03-22T08:12:18.249Z",
    "done_reason": "filled",
    "fill_fees": "0.0024935680000000",
    "filled_size": "0.00011132",
    "executed_value": "0.9974272000000000",
    "status": "done",
    "settled": true
    }
    """
    product = GdaxProduct.FromStrNativeProductNothrow(order_msg['product_id'])
    if product is None or 'price' not in order_msg:
      return

    price = float(order_msg['price'])
    qty = float(order_msg['size'])
    return ProductOrderElement(
        symbol=product.symbol,
        price=price,
        qty=qty,
        order_id=order_msg['id'],
    )

  @staticmethod
  def parse_product_orders(order_list_msg, product):
    print(order_list_msg)
    order_list = []
    for order_msg in order_list_msg:
      order = GdaxPrivateParser.parse_order(order_msg)
      if order is not None:
        order_list.append(order)
    return ProductOrderBundle(exchange='Gdax', symbol=product.symbol, each_order=order_list)

  @staticmethod
  def parse_transfers(withdraw_update_msg, deposite_update_msg):

    def _parse_transfers_status(update_msg):
      if 'internal' in update_msg['type']:
        return 'TRANSFER_CONFIRMED'
      if update_msg['completed_at']:
        return 'TRANSFER_CONFIRMED'
      elif update_msg['canceled_at']:
        return 'CANCEL_CONFIRMED'
      elif update_msg['processed_at']:
        return 'TRANSFER_ACCEPTED'
      else:
        return 'TRANSFER_REJECTED'

    transfer_list = []
    assert isinstance(withdraw_update_msg, list), withdraw_update_msg
    assert isinstance(deposite_update_msg, list), deposite_update_msg
    for transfer_elem in withdraw_update_msg:
      details = transfer_elem.get('details')
      transfer = CurrencyTransferElement(
          currency=transfer_elem.get('currency', None),
          type='WITHDRAW',
          to_address=details.get('sent_to_address', None) if details else None,
          qty=float(transfer_elem['amount']),
          fee=float(details.get('fee', 0)) if details else 0,
          external_transfer_id=transfer_elem['id'],
          status=_parse_transfers_status(transfer_elem),
          create_timestamp=int(dateutil.parser.parse(transfer_elem['created_at']).timestamp() * 1e9)
        )
      transfer_list.append(transfer)
    for transfer_elem in deposite_update_msg:
      details = transfer_elem.get('details')
      transfer = CurrencyTransferElement(
          currency=transfer_elem.get('currency', None),
          type='DEPOSIT',
          from_address=details.get('crypto_address', None) if details else None,
          qty=float(transfer_elem['amount']),
          fee=float(details.get('fee', 0)) if details else 0,
          external_transfer_id=transfer_elem['id'],
          status=_parse_transfers_status(transfer_elem),
          create_timestamp=int(dateutil.parser.parse(transfer_elem['created_at']).timestamp() * 1e9)
        )
      transfer_list.append(transfer)
    return CurrencyTransferBundle(exchange='Gdax',
                                  market_type='Spot',
                                  currency=None,
                                  native_currency=None,
                                  each_transfer=transfer_list)

class GdaxPrivateClient(privcb.PrivateClientBase):
  ProductType = GdaxProduct

  def __init__(self, key_file, timeout=5):
    privcb.PrivateClientBase.__init__(self)
    self.nprivc = GdaxNativePrivateClient(key_file=key_file, timeout=timeout)

  def query_account_balance_impl(self):
    update = self.nprivc.query_accounts()
    update.msg = GdaxPrivateParser.parse_account_balance(update.msg)
    return update

  def query_list_orders_impl(self, product):
    update = self.nprivc.query_open_orders(status=GdaxOrderStatus.DONE,
                                           product_id=product.native_symbol)
    update.msg = GdaxPrivateParser.parse_product_orders(update.msg, product)
    return update

  def query_account_transfer_impl(self, currency=None):
    withdraw_update = self.nprivc.query_withdraw_history()
    deposit_update = self.nprivc.query_deposit_history()
    update = deposit_update
    update.msg = GdaxPrivateParser.parse_transfers(withdraw_update.msg,
                                                   deposit_update.msg)
    return update

  def query_cancel_product_impl(self, product):
    raise NotImplementedError

  def query_fills_impl(self, product):
    raise NotImplementedError

  def query_submit_impl(self, product, side, price, qty):
    raise NotImplementedError
